package com.imooc.newretail.common.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Bean转换工具类
 * @author Hank
 */
@UtilityClass
public class BeanUtils extends BeanUtil {
    private static final Logger log = LoggerFactory.getLogger(BeanUtils.class);

    /**
     * 转换Entity List成Dto List
     *
     * @param sources List<Entity>
     * @param target  Class<Dto>
     * @return List<Dto>
     */
    public static <S, D> List<D> copyPropertiesForList(final List<S> sources, Class<D> target) {
        List<D> dests = new ArrayList<>();
        if(CollUtil.isNotEmpty(sources)){
            for (S source : sources) {
                D instance = null;
                try {
                    instance = target.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }

                copyProperties(source, instance);
                //设置is字段
                convertIsField(source, instance);

                dests.add(instance);
            }
        }
        return dests;
    }

    /**
     * 根据类型转换
     *
     * @param source S
     * @param target D
     * @return new instance D
     */
    public static <S, D> D copyProperties(S source, Class<D> target) {
        if(source == null){
            return null;
        }

        D instance = null;
        try {
            instance = target.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        copyProperties(source, instance);
        //设置is字段
        convertIsField(source, instance);

        return instance;
    }

    public static void copyBeanIgnoreNull(Object source, Object target) {
        copyProperties(source, target, CopyOptions.create().setIgnoreNullValue(true).setIgnoreError(true));
    }

    private static <S, D> void convertIsField(S source, D target){
        if(source != null){
            Field[] fields = source.getClass().getDeclaredFields();
            Method[] methods = target.getClass().getDeclaredMethods();
            for(Field field : fields){
                if(StringUtils.startsWith(field.getName(), "is")){
                    for(Method method : methods){
                        if(StringUtils.equals(method.getName(), "set" + upperFistChar(field.getName()))){
                            try {
                                method.invoke(target, BeanUtils.getFieldValue(source, field.getName()));
                            } catch (Exception e) {
                                log.warn("设置属性失败，source：{}，target：{}，field：{}", source.getClass(), target.getClass(), field.getName());
                            }
                        }
                    }
                }
            }
        }
    }

    private static String upperFistChar(String str) {
        char[] cs=str.toCharArray();
        cs[0]-=32;
        return String.valueOf(cs);
    }

    /**
     * 将key替换为没有下划线的驼峰命名
     * @param  type
     * @param  map
     * @return T	[返回类型说明]
     * @exception/throws [违例类型] [违例说明]
     * @see   [类、类#方法、类#成员]
     * @author gaolinlou
     * @time 2016年12月14日 下午8:07:55
     * @update 2016年12月14日 下午8:07:55
     */
    public static <T> T convertMapForKeyUnderline(Class<T> type, Map<String, Object> map) {
        Map<String, Object> newMap = dealWithKey(map);
        return convertMap(type,newMap);
    }

    @SuppressWarnings("all")
    public static <T> T convertMap(Class<T> type, Map map) {
        T obj = null;
        try {
            obj =mapToBean(map,type,true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 将key中的下划线替换为空
     * 注意：中间的下划线替换后会将后面的字母大写，满足驼峰命名规则
     * @param  values 设备原始数据
     * @see   [类、类#方法、类#成员]
     * @author gaolinlou
     * @time 2016年12月14日  上午11:30:24
     * @update 2016年12月14日 上午11:30:24
     */
    private static Map<String, Object> dealWithKey(Map<String, Object> values){
        if(values == null || values.isEmpty()){
            return null;
        }
        Map<String,Object> converMap = new HashMap<String,Object>();
        Iterator<String> iterator = values.keySet().iterator();
        while(iterator.hasNext()){
            String key = iterator.next();
            Object value = values.get(key);
            converMap.put(replaceUnderlineAndfirstToUpper(key), value);
        }
        return converMap;
    }

    private static String replaceUnderlineAndfirstToUpper(String key){
        StringBuffer sb = new StringBuffer();
        sb.append(key);
        int count = sb.indexOf("_",1);//以"_"开头的后面字母不大写
        while(count!=0){
            int num = sb.indexOf("_",count);
            count = num+1;
            if(num != -1){
                String s=sb.substring(count, count+1);
                sb.replace(count,count+1,s.toUpperCase());
            }
        }
        String ss = sb.toString().replaceAll("_","");
        return ss;
    }

    /**
     * 空白字符串转为null
     *
     * @name emptyStringToNull(这里用一句话描述这个方法的作用) (这里描述这个方法适用条件 – 可选)
     * @param bean
     * @return
     * @autor leicheng
     * @time 2015年10月30日 下午3:39:55
     * @update 2015年10月30日 下午3:39:55
     *
     */
    public static <T> T emptyStringToNull(T bean) {
        if (bean == null) {
            return null;
        } else {
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                int modifiers = declaredField.getModifiers();
                if (declaredField.getType().equals(String.class)
                        && !Modifier.isFinal(modifiers)
                        && !Modifier.isStatic(modifiers)) {
                    try {
                        declaredField.setAccessible(true);
                        String str = (String) declaredField.get(bean);
                        if (str != null && str.trim().isEmpty()) {
                            declaredField.set(bean, null);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            return bean;
        }
    }

    public static Map convertBeanToMap(Object bean) throws IntrospectionException,
            IllegalAccessException, InvocationTargetException {
        Class type = bean.getClass();
        Map returnMap = new HashMap();
        BeanInfo beanInfo = Introspector.getBeanInfo(type);

        PropertyDescriptor[] propertyDescriptors = beanInfo
                .getPropertyDescriptors();
        for (int i = 0; i < propertyDescriptors.length; i++) {
            PropertyDescriptor descriptor = propertyDescriptors[i];
            String propertyName = descriptor.getName();
            if (!propertyName.equals("class")) {
                Method readMethod = descriptor.getReadMethod();
                Object result = readMethod.invoke(bean, new Object[0]);
                if (result != null) {
                    returnMap.put(propertyName, result);
                } else {
                    returnMap.put(propertyName, "");
                }
            }
        }
        return returnMap;
    }

    /**
     * 实例化所有类
     * @name newInstances(这里用一句话描述这个方法的作用) (这里描述这个方法适用条件 – 可选)
     * @param classes 类的集合
     * @param springContext 不为空时将会先尝试从spring内取出对象
     * @return
     * @autor wenxintao
     * @time 2015年8月20日 上午10:05:39
     * @update 2015年8月20日 上午10:05:39
     *
     */
    public static List<Object> newInstances(List<Class<?>> classes, ApplicationContext springContext) {
        List<Object> result = new ArrayList<>();
        for (Class<?> clazz : classes) {
            try {
                // 先从Spring内取出对象，取不到则实例化一个
                Object newInstance = null;
                if (springContext != null) {
                    try {
                        newInstance = springContext.getBean(clazz);
                    } catch (NoSuchBeanDefinitionException e) {
                        newInstance = null;
                    }
                }
                if (newInstance == null) {
                    newInstance = clazz.newInstance();
                }
                result.add(newInstance);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 扫描指定包下所有类
     * @param packageName 包名，如java.util
     * @param baseClass 父类/接口 类型，为空则为Object
     * @autor wenxintao
     * @time 2015年8月20日 上午10:04:27
     * @update 2015年8月20日 上午10:04:27
     */
    public static synchronized List<Class<?>> scanBackage(String packageName, Class<?> baseClass) {
        List<Class<?>> result = new ArrayList<>();
        if (baseClass == null) {
            baseClass = Object.class;
        }
        // 初始化扫描器
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(Thread.currentThread().getContextClassLoader());
        // 定义匹配字符串
        String patten = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + packageName.replace(".", "/") + "/**/*.class";
        try {
            // 扫描所有类
            Resource[] resources = resolver.getResources(patten);
            for (Resource resource : resources) {
                // 拼接类名
                String className = packageName + "." + resource.getFilename().replaceAll(".class$", "");
                try {
                    Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                    if (baseClass.isAssignableFrom(clazz)) {
                        result.add(clazz);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

}
